import { spawn, type SpawnOptions } from "node:child_process"
import { env } from "node:process"

export async function run(command: string, options?: SpawnOptions): Promise<number>
export async function run(command: string, args: string[], options?: SpawnOptions): Promise<number>
export async function run(
  command: string,
  argsOrOptions?: string[] | SpawnOptions,
  options?: SpawnOptions,
): Promise<number> {
  const argsMode = Array.isArray(argsOrOptions)
  const args = argsMode ? argsOrOptions : []
  const rawOptions = argsMode ? options || {} : argsOrOptions || {}
  const finalOptions: SpawnOptions = {
    stdio: "inherit" as const,
    shell: true,
    ...rawOptions,
    env: rawOptions.env || env,
  }
  const child = args.length > 0 ? spawn(command, args, finalOptions) : spawn(command, finalOptions)
  return new Promise((resolve, reject) => {
    child.on("error", reject)
    child.on("close", (code) => resolve(code || 0))
  })
}

export async function exec(command: string, options?: SpawnOptions): Promise<Buffer>
export async function exec(command: string, args: string[], options?: SpawnOptions): Promise<Buffer>
export async function exec(
  command: string,
  argsOrOptions?: string[] | SpawnOptions,
  options?: SpawnOptions,
): Promise<Buffer> {
  const argsMode = Array.isArray(argsOrOptions)
  const args = argsMode ? argsOrOptions : []
  const rawOptions = argsMode ? options || {} : argsOrOptions || {}
  const finalOptions: SpawnOptions = {
    stdio: ["pipe", "pipe", "pipe"] as const,
    shell: true,
    ...rawOptions,
    env: rawOptions.env || env,
  }
  const child = args.length > 0 ? spawn(command, args, finalOptions) : spawn(command, finalOptions)
  return new Promise((resolve, reject) => {
    const stdoutChunks: Buffer[] = []
    const stderrChunks: Buffer[] = []
    child.stdout?.on("data", (chunk: Buffer) => stdoutChunks.push(chunk))
    child.stderr?.on("data", (chunk: Buffer) => stderrChunks.push(chunk))
    child.on("error", reject)
    child.on("close", (code: number | null) => {
      if (code) {
        const stderr = Buffer.concat(stderrChunks).toString("utf8")
        reject(new Error(`Command failed (${code}): ${stderr}`))
      }
      resolve(Buffer.concat(stdoutChunks))
    })
  })
}
